package com.example.tcp.server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
@Component
public class DecoderFactory implements ApplicationListener<ApplicationReadyEvent> {

    private List<Decoder> decoders = new ArrayList<>();

    public void add(Decoder decoder) {
        for (int i = 0; i < decoders.size(); i++) {
            if(decoder.id().equals(decoders.get(i).id())) {
                log.warn("add duplicate decoder: {}", decoder.id());
                return;
            }
        }
        decoders.add(decoder);
    }

    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        boolean accept = false;
        for (int i = 0; i < decoders.size(); i++) {
            Decoder d = decoders.get(i);
            if(d.accept(ctx, in)) {
                accept = true;
                int length = d.length(ctx, in);
                if(length < 0) {
                    // 非法报文,强制关闭
                    log.warn("recv illegal length frame, close channel: {}", ctx.channel());
                    ctx.close();
                } else if(length > 0) {
                    out.add(d.createFrame(ctx, in.readRetainedSlice(length)));
                }
                break;
            }
        }
        // 非法报文,强制关闭
        if(!accept && in.readableBytes() >= 1024 * 5) {
            log.warn("recv illegal frame, close channel: {}", ctx.channel());
            ctx.close();
        }
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        Collection<Decoder> ds  = event.getApplicationContext().getBeansOfType(Decoder.class).values();
        ds.forEach(this::add);
    }

    public interface Decoder {
        String id();
        boolean accept(ChannelHandlerContext ctx, ByteBuf in);
        int length(ChannelHandlerContext ctx, ByteBuf in);
        Frame createFrame(ChannelHandlerContext ctx, ByteBuf in);
    }
}
